<?php


namespace paySdk\core;


class RsaOperation
{
    public static $instance = null;
    protected $_private_key_dir = '';
    protected $public_key_dir = '';

    /**
     * RsaOperation constructor.
     * @param string $public_pem
     * @param string $private_pem
     * @throws \Exception
     */
    private function __construct($public_pem = '', $private_pem = '')
    {
        $this->public_key_dir = !empty($public_pem) ? trim($public_pem) : '';
        $this->_private_key_dir = !empty($private_pem) ? trim($private_pem) : '';
        if(empty($this->public_key_dir) && empty($this->_private_key_dir))
            throw new \Exception("公私钥不能同时为空");
    }

    /**
     * @param string $public_pem
     * @param string $private_pem
     * @return static|null
     * @throws \Exception
     */
    static public function getInstance($public_pem = '', $private_pem = ''){
        if(is_null(self::$instance))
            self::$instance = new static($public_pem, $private_pem);

        return self::$instance;
    }

    /**
     * @throws \Exception
     */
    public function __clone(){
        throw new \Exception("不允许clone");
    }

    /**
     * 设置私钥
     * @param string $private_pem
     * @return $this
     * @throws \Exception
     */
    public function setPrivateKeyPath(string $private_pem = ''): RsaOperation
    {
        if (empty($private_pem))
            throw new \Exception("私钥证书路径不能为空");
        if (file_exists($private_pem))
            throw new \Exception("私钥证书不存在");

        $this->_private_key_dir = $private_pem;

        return $this;
    }

    /**
     * 设置公钥
     * @param string $public_pem
     * @return $this
     * @throws \Exception
     */
    public function setPublicKeyPah(string $public_pem = ''): RsaOperation
    {
        if (empty($public_pem))
            throw new \Exception("公钥证书路径不能为空");
        if (! file_exists($public_pem))
            throw new \Exception("公钥证书不存在");

        $this->public_key_dir = $public_pem;

        return $this;
    }

    /**
     * 读取私钥
     * @param string $pass
     * @return resource
     * @throws \Exception
     */
    protected function getPrivateKey(string $pass = '')
    {
        $_private_key_dir = file_get_contents($this->_private_key_dir);
        //这个函数可用来判断私钥是否是可用的，可用返回资源id Resource id
        $private_key = openssl_pkey_get_private($_private_key_dir, $pass);
        if (! $private_key)
            throw new \Exception("无效私钥证书!!");

        return $private_key;
    }

    /**
     * 读取公钥
     * @return resource
     * @throws \Exception
     */
    protected function getPublicKey(){
        $public_key_dir = file_get_contents($this->public_key_dir);
        //判断公钥是否是可用的
        $public_key = openssl_pkey_get_public($public_key_dir);
        if (! $public_key)
            throw new \Exception("无效公钥证书!!");

        return $public_key;
    }


    /**
     * 私钥加密
     * @param string $original_string 需要加密的数据
     * @return bool|string
     * @throws \Exception
     */
    public function privateEncrypt(string $original_string){
        if(empty($original_string))
            throw new \Exception("无效待加密参数!");

        $encrypted = $return = $encrypt_str = "";
        for ($i = 0; $i < 10000;) {
            if ($i <= strlen($original_string)) {
                $step = 500;
                $encrypt_str = substr($original_string, $i, $step);
                openssl_private_encrypt($encrypt_str, $encrypted, $this->getPrivateKey());
                $return .= base64_encode($encrypted);
            } else {
                break;
            }
            $i = $i + $step;
        }
        return $return;
    }

    /**
     * 公钥解密
     * @param string $encrypted_string 需要解密的内容
     * @return bool
     * @throws \Exception
     */
    public function publicDecrypt(string $encrypted_string){
        if(empty($encrypted_string) || !is_string($encrypted_string))
            throw new \Exception("无效待解密数据");

        $res_arr = explode('=', $encrypted_string);
        $return = '';
        foreach ($res_arr as $val) {
            if (! empty($val)) {
                $decrypted = '';
                openssl_public_decrypt(base64_decode($val . '='), $decrypted, $this->getPublicKey());
                $return .= $decrypted;
            }
        }
        return $return;
    }

    /**
     * 公钥加密
     * @param string $original_string 需要加密的数据
     * @return false|string
     * @throws \Exception
     */
    public function publicEncrypt(string $original_string){
        if(empty($original_string))
            throw new \Exception("无效待加密参数");

        $encrypted = $return = $encrypt_str = "";
        for ($i = 0; $i < 10000;) {
            if ($i <= strlen($original_string)) {
                $step = 500;
                $encrypt_str = substr($original_string, $i, $step);
                openssl_public_encrypt($encrypt_str, $encrypted, $this->getPublicKey());
                $return .= base64_encode($encrypted);
            } else {
                break;
            }
            $i = $i + $step;
        }
        return $return;
    }

    /**
     * 私钥解密
     * @param string $string_encrypted 需要解密的内容
     * @return false|string
     * @throws \Exception
     */
    public function privateDecrypt(string $string_encrypted)
    {
        if (empty($string_encrypted) || !is_string($string_encrypted)) return false;

        try {
            $res_arr = explode('=', $string_encrypted);
            $return = '';
            foreach ($res_arr as $val) {
                if (! empty($val)) {
                    $decrypted = '';
                    openssl_private_decrypt(base64_decode($val . '='), $decrypted, $this->getPrivateKey());
                    $return .= $decrypted;
                }
            }
            return $return;
        } catch (\Exception $e) {
            throw new \Exception("无效私钥证书");
        }

    }

    /**
     * RSA 私钥签名
     * @param string $original_str 签名数据
     * @param string $pass 密码
     * @return bool|string
     * @throws \Exception
     */
    public function sign(string $original_str, string $pass = '') {
        try {
            $private_key = $this->getPrivateKey($pass);

            openssl_sign($original_str, $sign, $private_key);
            openssl_free_key($private_key);

            return base64_encode($sign);
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * RSA 公钥验签
     * @param $sign
     * @param $original_str
     * @return bool
     * @throws Exception
     */
    public function verify($sign, $original_str) {
        try {

            $this->getPublicKey();
            $public_content=file_get_contents($this->public_key_dir);

            $public_key=openssl_get_publickey($public_content);

            $sign=base64_decode($sign);//得到的签名

            $result=(bool)openssl_verify($original_str, $sign, $public_key);

            openssl_free_key($public_key);

            return $result;

        } catch (\Exception $e) {
            throw $e;
        }

    }
}